package com.hk.commons.utils.yingmi;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.annotation.PostConstruct;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class YingmiApiUtils {

	private final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
	@Value("${yingmi.host}")
	private String host;

	@Value("${yingmi.apiKey}")
	private String apiKey;

	@Value("${yingmi.apiSecret}")
	private String apiSecret;

	@Value("${yingmi.keyStorePath}")
	private String keyStorePath;

	@Value("${yingmi.keyStorePassword}")
	private String keyStorePassword;

	@Value("${yingmi.trustStorePath}")
	private String trustStorePath;

	@Value("${yingmi.trustStorePassword}")
	private String trustStorePassword;

	@Value("${yingmi.cacert.crt}")
	private String yingMiCacert;

	@Value("${yingmi.cacert.crtPassword}")
	private String yingMiCacertPassword;

	private Logger logger = LoggerFactory.getLogger(YingmiApiUtils.class);

	private SSLConnectionSocketFactory sf = null;

	@PostConstruct
	public void init() {
		// load key store
		try {
			KeyStore ks = KeyStore.getInstance("jks");
			ks.load(new FileInputStream(new File(this.yingMiCacert)), this.yingMiCacertPassword.toCharArray());

			SSLContext sslContext = SSLContexts.custom()
					.loadKeyMaterial(ks, this.yingMiCacertPassword.toCharArray()).build();

//			InputStream is = new java.io.ByteArrayInputStream( java.util.Base64.getDecoder().decode(this.yingMiCacert) );
//			InputStream is = new FileInputStream(this.yingMiCacert);
//
//			CertificateFactory cf = CertificateFactory.getInstance("X.509");
//			X509Certificate caCert = (X509Certificate)cf.generateCertificate(is);
//
//			TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509", "SunJSSE");
//
//			KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
//			ks.load(null);
//			KeyStore.Entry caCertEntry = new KeyStore.TrustedCertificateEntry(caCert);
//			ks.setEntry("caCert", caCertEntry, null);
//			ks.setCertificateEntry("mykey", caCert);
//
//			tmf.init(ks);

//			SSLContext sslContext = SSLContext.getInstance("TLSv1.2", "SunJSSE");
//			sslContext.init(null, tmf.getTrustManagers(), null);

//			SSLContext context = SSLContexts
//					.custom()
//					.loadKeyMaterial(new File(this.keyStorePath),
//							keyStorePassword.toCharArray(),
//							keyStorePassword.toCharArray())
//					.loadTrustMaterial(new File(trustStorePath),
//							trustStorePassword.toCharArray()).build();
			sf = new SSLConnectionSocketFactory(sslContext,
					new String[] { "TLSv1.2" }, null,
					SSLConnectionSocketFactory.getDefaultHostnameVerifier());
			/*
			 * PoolingHttpClientConnectionManager cm = new
			 * PoolingHttpClientConnectionManager(); //连接池最大生成连接数200
			 * cm.setMaxTotal(200); // 默认设置route最大连接数为20
			 * cm.setDefaultMaxPerRoute(20); // 指定专门的route，设置最大连接数为80 HttpHost
			 * localhost = new HttpHost("locahost", 80); cm.setMaxPerRoute(new
			 * HttpRoute(localhost), 50); // 创建httpClient httpClient =
			 * HttpClients.custom()
			 * .setConnectionManager(cm).setSSLSocketFactory(sf).build();
			 */

		} catch (KeyStoreException | NoSuchAlgorithmException
				| CertificateException | IOException
				| UnrecoverableKeyException | KeyManagementException e) {
			logger.error("Init Yingmi TLS1.2 error :", e);
		}
	}

	public String get(String path, Map<String, String> params) {
		String basePath = "/v1";
		URIBuilder builder = new URIBuilder().setScheme("https").setHost(host)
				.setPath(basePath + path);

		addRequiredParams("GET", path, params, apiKey, apiSecret);

		for (String key : params.keySet()) {
			builder.setParameter(key, params.get(key).toString());
		}
		
		CloseableHttpResponse resp = null;
		CloseableHttpClient httpClient = null;
		try {
			URI uri = builder.build();

			HttpGet httpGet = new HttpGet(uri);
			
			httpClient = HttpClients.custom().setMaxConnPerRoute(200)
					.setSSLSocketFactory(sf).build();
//			logger.info(httpGet.getURI().toString());
			resp = httpClient.execute(httpGet);
			if (resp.getStatusLine().getStatusCode() >= 300) {
				System.err.println("Something wrong: "
						+ resp.getStatusLine().toString());
			}
			BufferedReader input = new BufferedReader(new InputStreamReader(
					resp.getEntity().getContent(), "UTF-8"));

			StringBuilder sb = new StringBuilder();
			char[] buf = new char[1000];
			int count;

			while ((count = input.read(buf)) > 0) {
				sb.append(buf, 0, count);
			}
			return sb.toString();
		} catch (IOException | URISyntaxException e) {
			throw new RuntimeException(e);
		}finally {
			try {
				if (resp != null) {
					resp.close();
				}
				if (httpClient != null) {
					httpClient.close();
				}
			} catch (IOException e) {
				logger.error("post httpClient close error:{}", e);
			}
		}
	}
	
	
	public String fileGet(String path, Map<String, String> params , String host) {
		String basePath = "/v1";
		URIBuilder builder = new URIBuilder().setScheme("https").setHost(host)
				.setPath(basePath + path);

		addRequiredParams("GET", path, params, apiKey, apiSecret);

		for (String key : params.keySet()) {
			builder.setParameter(key, params.get(key).toString());
		}
		
		CloseableHttpResponse resp = null;
		CloseableHttpClient httpClient = null;
		try {
			URI uri = builder.build();

			HttpGet httpGet = new HttpGet(uri);
			
			httpClient = HttpClients.custom().setMaxConnPerRoute(200)
					.setSSLSocketFactory(sf).build();
			
			resp = httpClient.execute(httpGet);
			if (resp.getStatusLine().getStatusCode() >= 300) {
				System.err.println("Something wrong: "
						+ resp.getStatusLine().toString());
			}
			BufferedReader input = new BufferedReader(new InputStreamReader(
					resp.getEntity().getContent(), "UTF-8"));

			StringBuilder sb = new StringBuilder();
			char[] buf = new char[1000];
			int count;

			while ((count = input.read(buf)) > 0) {
				sb.append(buf, 0, count);
			}
			return sb.toString();
		} catch (IOException | URISyntaxException e) {
			throw new RuntimeException(e);
		}finally {
			try {
				if (resp != null) {
					resp.close();
				}
				if (httpClient != null) {
					httpClient.close();
				}
			} catch (IOException e) {
				logger.error("post httpClient close error:{}", e);
			}
		}
	}
	

	public String post(String path, Map<String, String> params) {
		String basePath = "/v1";
		URIBuilder builder = new URIBuilder().setScheme("https").setHost(host)
				.setPath(basePath + path);
		// clear the params with empty value
		Map<String, String> trimmedParams = new HashMap<>();
		for (String key : params.keySet()) {
			if (params.get(key) != null) {
				trimmedParams.put(key, params.get(key));
			}
		}
		addRequiredParams("POST", path, trimmedParams, apiKey, apiSecret);
		CloseableHttpResponse resp = null;
		CloseableHttpClient httpClient = null;
		try {
			URI uri = builder.build();
			RequestBuilder requestBuilder = RequestBuilder.post(uri);
			List<NameValuePair> kvs = new ArrayList<>();
			for (String key : trimmedParams.keySet()) {
				kvs.add(new BasicNameValuePair(key, trimmedParams.get(key)));
			}
			requestBuilder.setEntity(new UrlEncodedFormEntity(kvs, "UTF-8"));
			HttpUriRequest request = requestBuilder.build();

			httpClient = HttpClients.custom().setMaxConnPerRoute(200)
					.setSSLSocketFactory(sf).build();

			resp = httpClient.execute(request);

			if (resp.getStatusLine().getStatusCode() >= 300) {
				System.err.println("Something wrong: "
						+ resp.getStatusLine().toString());
			}
			BufferedReader input = new BufferedReader(new InputStreamReader(
					resp.getEntity().getContent(), "UTF-8"));
			StringBuilder sb = new StringBuilder();
			char[] buf = new char[1000];
			int count;
			while ((count = input.read(buf)) > 0) {
				sb.append(buf, 0, count);
			}
			return sb.toString();
		} catch (IOException | URISyntaxException e) {
			throw new RuntimeException(e);
		} finally {
			try {
				if (resp != null) {
					resp.close();
				}
				if (httpClient != null) {
					httpClient.close();
				}
			} catch (IOException e) {
				logger.error("post httpClient close error:{}", e);
			}
		}
	}

	void addRequiredParams(String method, String path,
			Map<String, String> params, String apiKey, String apiSecret) {

		logger.info("path = {}", path);
		params.put("key", apiKey);
		params.put("sigVer", "1");
		String ts = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
				.format(LocalDateTime.now());
		params.put("ts", ts);
		params.put("nonce", RandomStringUtils.randomAlphanumeric(16));
		String sig = getSig(method, path, apiSecret, params);
		params.put("sig", sig);
	}

	String getSig(String method, String path, String apiSecret,
			Map<String, String> params) {
		StringBuilder sb = new StringBuilder();
		Set<String> keySet = new TreeSet<>(params.keySet());
		for (String key : keySet) {
			String value = params.get(key);
			if (value == null) {
				continue;
			}
			sb.append(key);
			sb.append("=");
			sb.append(params.get(key));
			sb.append("&");
		}
		sb.setLength(sb.length() - 1); // trim the last "&"
		String unifiedString = method.toUpperCase() + ":" + path + ":"
				+ sb.toString();
		logger.debug("unified string: " + unifiedString);

		// calc hmac sha1
		try {
			SecretKeySpec secret = new SecretKeySpec(apiSecret.getBytes(),
					HMAC_SHA1_ALGORITHM);
			Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
			mac.init(secret);
			byte[] hmac = mac.doFinal(unifiedString.getBytes("UTF-8")); // UTF8

			// base64 encode the hmac
			String sig = Base64.getEncoder().encodeToString(hmac);
			logger.debug("signature: " + sig);
			return sig;

		} catch (NoSuchAlgorithmException | InvalidKeyException  | UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}
}
